package evemanutool.utils.databases; import java.io.File; import java.io.FileNotFoundException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Scanner; import javax.swing.JLabel; import javax.swing.JProgressBar; import evemanutool.constants.DBConstants; import evemanutool.constants.UserPrefConstants; import evemanutool.data.cache.MarketInfoEntry; import evemanutool.data.cache.TradeEntry; import evemanutool.data.cache.TradeHistoryEntry; import evemanutool.data.transfer.TradeHistoryQuery; import evemanutool.gui.main.EMT; import evemanutool.prefs.Preferences; import evemanutool.prefs.Preferences.MarketSetting; import evemanutool.prefs.Preferences.MarketSystem; import evemanutool.utils.datahandling.Database; import evemanutool.utils.datahandling.DatabaseHandler.Stage; import evemanutool.utils.httpdata.EVECentralWorker; import evemanutool.utils.httpdata.EVEMarketDataWorker; public class PriceDB extends Database implements DBConstants, UserPrefConstants { //Constants. private static final long MARKET_CHECK_DELAY = 50; // Ms. //DB:s private BlueprintDB bdb; private Preferences prefs; //GUI private JProgressBar marketPB; private JLabel marketStatus; private JProgressBar historyPB; private JLabel historyStatus; //Data //Market, EVE-Central. private HashMap<Integer, MarketInfoEntry> dbms = new HashMap<Integer, MarketInfoEntry>(); //Sell. private HashMap<Integer, MarketInfoEntry> dbmb = new HashMap<Integer, MarketInfoEntry>(); //Buy. //History, EVE-marketdata. private HashMap<Integer, TradeHistoryEntry> dbhs = new HashMap<Integer, TradeHistoryEntry>(); //Sell. private HashMap<Integer, TradeHistoryEntry> dbhb = new HashMap<Integer, TradeHistoryEntry>(); //Buy. //List of all possible query ids. private ArrayList<Integer> historyQueryIds; private ArrayList<Integer> marketQueryIds; //DB status. //Query state. private Date lastQuery; //Query workers. private EVECentralWorker marketWorker; private EVEMarketDataWorker historyWorker; //Lock objects. private Object marketLock = new Object(); private Object historyLock = new Object(); private Object queryTimeLock = new Object(); public PriceDB() { super(true, true, Stage.PROCESS, Stage.COMPUTE); } public void init(BlueprintDB bdb, Preferences prefs, JProgressBar marketPB, JLabel marketStatus, JProgressBar historyPB, JLabel historyStatus) { this.bdb = bdb; this.prefs = prefs; this.marketPB = marketPB; this.marketStatus = marketStatus; this.historyPB = historyPB; this.historyStatus = historyStatus; } @Override public synchronized void loadRawData() throws Exception { //Catch errors here since they are not critical to operation. Scanner sc; try { sc = new Scanner(new File(MARKET_CACHE_PATH)); while (sc.hasNextLine()) { String[] infoStr = sc.nextLine().split(LEVEL1_DELIM); addSMI(new MarketInfoEntry().fromParseString(infoStr[0])); addBMI(new MarketInfoEntry().fromParseString(infoStr[1])); } sc.close(); } catch (Exception e) { System.err.println("Could not read market file cache: " + e.getMessage()); } String[] infoStr = null; try { sc = new Scanner(new File(HISTORY_CACHE_PATH)); while (sc.hasNextLine()) { infoStr = sc.nextLine().split(LEVEL1_DELIM); addSTH(new TradeHistoryEntry().fromParseString(infoStr[0]), true); addBTH(new TradeHistoryEntry().fromParseString(infoStr[1]), true); } sc.close(); } catch (Exception e) { System.err.println("Could not read history file cache: " + e.getMessage()); } //Show message. EMT.M_HANDLER.addMessage("Market data loaded from cache. " + (dbmb.size() + dbms.size()) + " price entries added and " + (dbhb.size() + dbhs.size()) + " history entries added."); } @Override public synchronized void processData() throws Exception { //Make sure all queries are added. historyQueryIds = bdb.getMarketQueryIds(); marketQueryIds = bdb.getMinMarketQueryIds(); //Interrupt the query threads. if (marketWorker != null) { marketWorker.cancel(true); } if (historyWorker != null) { historyWorker.cancel(true); } //Temporary variables. ArrayList<Integer> mSL = new ArrayList<>(); ArrayList<Integer> mBL = new ArrayList<>(); Date earliestAccepted; //Market //Set earliest date from preferences. earliestAccepted = new Date(System.currentTimeMillis() - (((long) prefs.getMarketSetting(MarketSetting.UPDATE_FREQ)) * 3600 * 1000)); //Get market locations. long sellLocation = Long.parseLong(MARKET_SYSTEM_CODE[prefs.getMarketSystemIndex(MarketSystem.SELL_SYSTEM)]); long buyLocation = Long.parseLong(MARKET_SYSTEM_CODE[prefs.getMarketSystemIndex(MarketSystem.BUY_SYSTEM)]); synchronized (marketLock) { //Add non existing types. for (Integer typeId : marketQueryIds) { if (!dbms.containsKey(typeId)) { mSL.add(typeId); } if (!dbmb.containsKey(typeId)) { mBL.add(typeId); } } //Add too old types or wrong location. for (MarketInfoEntry mI : dbms.values()) { if (mI.getDate().before(earliestAccepted) || mI.getLocationId() != sellLocation) { mSL.add(mI.getTypeId()); } } for (MarketInfoEntry mI : dbmb.values()) { if (mI.getDate().before(earliestAccepted) || mI.getLocationId() != buyLocation) { mBL.add(mI.getTypeId()); } } } //Start QueryWorker. startMarketUpdate(mSL, mBL); //History //Set earliest date from preferences. earliestAccepted = new Date(System.currentTimeMillis() - (((long) HISTORY_MAX_DAYS) * 24 * 3600 * 1000)); //Get market locations. sellLocation = Long.parseLong(MARKET_REGION_CODE[prefs.getMarketSystemIndex(MarketSystem.SELL_SYSTEM)]); buyLocation = Long.parseLong(MARKET_REGION_CODE[prefs.getMarketSystemIndex(MarketSystem.BUY_SYSTEM)]); //Check if database is incomplete. ArrayList<TradeHistoryQuery> sHL = new ArrayList<>(); ArrayList<TradeHistoryQuery> bHL = new ArrayList<>(); synchronized (historyLock) { int days; for (Integer typeId : historyQueryIds) { if (!dbhs.containsKey(typeId)) { //No history exists, add max amount. sHL.add(new TradeHistoryQuery(HISTORY_MAX_DAYS, typeId)); } if (!dbhb.containsKey(typeId)) { //No history exists, add max amount. bHL.add(new TradeHistoryQuery(HISTORY_MAX_DAYS, typeId)); } } //Check for outdated entries or wrong location. for (TradeHistoryEntry mH : dbhs.values()) { //Find the last entry to keep. for (int i = 0; i < mH.getHistory().size(); i++) { if (mH.getHistory().get(i).getDate().before(earliestAccepted)) { //Entry should not be included set history to sublist. mH.setHistory(mH.getHistory().subList(0, i)); break; } } //Add a query for the rest if difference is larger than one day. if (mH.getHistory().isEmpty() || mH.getLocationId() != sellLocation) { sHL.add(new TradeHistoryQuery(HISTORY_MAX_DAYS, mH.getTypeId())); } else if ((days = (int) ((System.currentTimeMillis() - mH.getHistory().get(0).getDate().getTime()) / (24 * 3600 * 1000))) > MINIMUM_HISTORY_UPDATE_NEED) { //Round down to avoid unnecessary data. //The number of days will reach back to the latest entry. sHL.add(new TradeHistoryQuery(days, mH.getTypeId())); } } for (TradeHistoryEntry mH : dbhb.values()) { //Find the last entry to keep. for (int i = 0; i < mH.getHistory().size(); i++) { if (mH.getHistory().get(i).getDate().before(earliestAccepted)) { //Entry should not be included set history to sublist. mH.setHistory(mH.getHistory().subList(0, i)); break; } } //Add a query for the rest if difference is larger than one day. if (mH.getHistory().isEmpty() || mH.getLocationId() != buyLocation) { bHL.add(new TradeHistoryQuery(HISTORY_MAX_DAYS, mH.getTypeId())); } else if ((days = (int) ((System.currentTimeMillis() - mH.getHistory().get(0).getDate().getTime()) / (24 * 3600 * 1000))) > MINIMUM_HISTORY_UPDATE_NEED) { //Round down to avoid unnecessary data. //The number of days will reach back to the latest entry. bHL.add(new TradeHistoryQuery(days, mH.getTypeId())); } } } //Start queries. startHistoryUpdate(sHL, bHL, false); //Wait for complete coverage, not necessarily entirely up to date. while (!isMIComplete() || !isTHComplete()) { //Wait until next check. Thread.sleep(MARKET_CHECK_DELAY); } //Last initiation step, set complete. super.setComplete(true); } @Override public void saveData() { try { //Interrupt the query threads. if (marketWorker != null) { marketWorker.cancel(true); } if (historyWorker != null) { historyWorker.cancel(true); } PrintWriter out = new PrintWriter(MARKET_CACHE_PATH); for (Integer id : marketQueryIds) { //Only save valid types. out.println(dbms.get(id).toParseString() + LEVEL1_DELIM + dbmb.get(id).toParseString()); } out.flush(); out.close(); out = new PrintWriter(HISTORY_CACHE_PATH); for (Integer id : historyQueryIds) { //Only save valid types. out.println(dbhs.get(id).toParseString() + LEVEL1_DELIM + dbhb.get(id).toParseString()); } out.flush(); out.close(); System.out.println("---Finished saving market data.---"); } catch (FileNotFoundException e1) { System.err.println(e1.getMessage()); } } @Override public void kill() { //Interrupt the query threads. if (marketWorker != null) { marketWorker.cancel(true); } if (historyWorker != null) { historyWorker.cancel(true); } } private void startMarketUpdate(List<Integer> sL, List<Integer> bL){ marketWorker = new EVECentralWorker(sL, bL, MARKET_SYSTEM_CODE[prefs.getMarketSystemIndex(MarketSystem.SELL_SYSTEM)], MARKET_SYSTEM_CODE[prefs.getMarketSystemIndex(MarketSystem.BUY_SYSTEM)], this, marketPB, marketStatus); marketWorker.execute(); } //Add MarketInfo. private void addSMI(MarketInfoEntry sellInfo) { synchronized (marketLock) { dbms.put(sellInfo.getTypeId(), sellInfo); //Update lastQuery date. setLastQuery(new Date()); } } //Add MarketInfo. private void addBMI(MarketInfoEntry buyInfo) { synchronized (marketLock) { dbmb.put(buyInfo.getTypeId(), buyInfo); //Update lastQuery date. setLastQuery(new Date()); } } public void addAllSMI(List<MarketInfoEntry> sellPrice) { for (int i = 0; i < sellPrice.size(); i++) { addSMI(sellPrice.get(i)); } } public void addAllBMI(List<MarketInfoEntry> buyPrice) { for (int i = 0; i < buyPrice.size(); i++) { addBMI(buyPrice.get(i)); } } //Returns the price of an item from typeId. public MarketInfoEntry getSellMI(int typeId) { synchronized (marketLock) { return dbms.get(typeId); } } //Returns the price of an item from typeId. public MarketInfoEntry getBuyMI(int typeId) { synchronized (marketLock) { return dbmb.get(typeId); } } private boolean isMIComplete() { synchronized (marketLock) { //Check for coverage. for (Integer typeId : marketQueryIds) { if (!dbms.containsKey(typeId) || !dbmb.containsKey(typeId)) { return false; } } return true; } } private void startHistoryUpdate(List<TradeHistoryQuery> sL, List<TradeHistoryQuery> bL, boolean forceReplace){ historyWorker = new EVEMarketDataWorker(sL, bL, forceReplace, MARKET_REGION_CODE[prefs.getMarketSystemIndex(MarketSystem.SELL_SYSTEM)], MARKET_REGION_CODE[prefs.getMarketSystemIndex(MarketSystem.BUY_SYSTEM)], this, historyPB, historyStatus); historyWorker.execute(); } //Add MarketHistory, should have the same typeId. private void addTH(HashMap<Integer, TradeHistoryEntry> db, TradeHistoryEntry history, boolean forceReplace) { synchronized (historyLock) { //Add a new entry i none exist. if (db.get(history.getTypeId()) == null) { db.put(history.getTypeId(), new TradeHistoryEntry(history.getTypeId(), history.getLocationId())); } if (forceReplace) { //Replace the entry. db.put(history.getTypeId(), history); } else { //Add entries to history. ArrayList<TradeEntry> tmp = db.get(history.getTypeId()).getHistory(); for (TradeEntry pHE : history.getHistory()) { if (!tmp.contains(pHE)) { tmp.add(pHE); } } } //Sort. Collections.sort(db.get(history.getTypeId()).getHistory()); } } //Add MarketHistory, should have the same typeId. private void addSTH(TradeHistoryEntry sellHistory, boolean forceReplace) { addTH(dbhs, sellHistory, forceReplace); } //Add MarketHistory, should have the same typeId. private void addBTH(TradeHistoryEntry buyHistory, boolean forceReplace) { addTH(dbhb, buyHistory, forceReplace); } public void addAllSTH(List<TradeHistoryEntry> sHL, boolean forceReplace) { for (int i = 0; i < sHL.size(); i++) { addSTH(sHL.get(i), forceReplace); } } public void addAllBTH(List<TradeHistoryEntry> bHL, boolean forceReplace) { for (int i = 0; i < bHL.size(); i++) { addBTH(bHL.get(i), forceReplace); } } //Returns the history of an item from typeId. public TradeHistoryEntry getSellTH(int typeId) { synchronized (historyLock) { return dbhs.get(typeId); } } //Returns the history of an item from typeId. public TradeHistoryEntry getBuyTH(int typeId) { synchronized (historyLock) { return dbhb.get(typeId); } } private boolean isTHComplete() { synchronized (historyLock) { //Check for coverage. for (Integer typeId : historyQueryIds) { if (!dbhs.containsKey(typeId) || !dbhb.containsKey(typeId)) { return false; } } return true; } } private void setLastQuery(Date lastQuery) { synchronized (queryTimeLock) { this.lastQuery = lastQuery; } } private Date getLastQuery() { synchronized (queryTimeLock) { return lastQuery; } } public boolean forceUpdateMarketData() { //Returns true or false depending on if the update was started. if (isComplete() && (getLastQuery() == null || System.currentTimeMillis() - getLastQuery().getTime() > MARKET_UPDATE_DELAY)) { //Market. //Interrupt the query thread if running. if (marketWorker != null) { marketWorker.cancel(true); } //Copy ArrayList<Integer> mQueries = new ArrayList<>(); for (Integer id : historyQueryIds) { mQueries.add(id); } startMarketUpdate(mQueries, mQueries); //History. //Interrupt the query thread if running. if (historyWorker != null) { historyWorker.cancel(true); } ArrayList<TradeHistoryQuery> tQueries = new ArrayList<>(); for (Integer id : historyQueryIds) { tQueries.add(new TradeHistoryQuery(HISTORY_MAX_DAYS, id)); } startHistoryUpdate(tQueries, tQueries, true); //Show message. EMT.M_HANDLER.addMessage("Market update forced. " + marketQueryIds.size() + " price entries queued and " + historyQueryIds.size() + " history entries queued."); return true; } return false; } public int getMinsToNextMarketUpdate() { if (getLastQuery() == null) { return 0; } return (int) (((MARKET_UPDATE_DELAY + getLastQuery().getTime() - System.currentTimeMillis()) / (60 * 1000)) + 0.5); } }